package com.pangus.toolkits.files;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.pangus.toolkits.files.enums.Scheme;
import com.pangus.toolkits.files.errors.uncheck.FSException;
import com.pangus.toolkits.files.errors.uncheck.InvalidCredentialsException;
import com.pangus.toolkits.files.errors.uncheck.InvalidEndpointException;
import com.pangus.toolkits.files.errors.uncheck.InvalidPortException;
import com.pangus.toolkits.files.http.NetworkClient;
import com.pangus.toolkits.files.http.Response;
import com.pangus.toolkits.files.model.FileInfo;
import com.pangus.toolkits.files.model.FileLink;
import com.pangus.toolkits.files.operation.FSFileOperation;
import com.pangus.toolkits.files.operation.FSSystemOperation;
import com.pangus.toolkits.files.result.StorageInfo;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author yingheng chen
 * @description 构建文件上传客户端
 * @date 2019/11/27
 */
public class FSClient implements FSAPI{

    /**
     * 连接时间 30秒
     */
    public static long DEFAULT_CONNECTION_TIMEOUT = 30;
    private final FSContext context;
    private final NetworkClient networkClient;
    private final FSSystemOperation systemOperation;
    private final FSFileOperation fileOperation;

    public FSClient(FSConfig config) {
        this(config, null);
    }

    public FSClient(FSConfig config, OkHttpClient client) {
        checkConfig(config);
        HttpUrl httpUrl = buildBaseUrl(config.getServerUrl(), config.getPort());
        final FSCredentials credentials = new FSCredentials(config.getAccessKey(), config.getSecretKey());
        if (credentials.isEmpty()) {
            throw new InvalidCredentialsException("credential can not be empty");
        }
        this.context = new FSContext(httpUrl, config, credentials);
        this.networkClient = buildClient(client);
        this.systemOperation = new FSSystemOperation(networkClient, context);
        this.fileOperation = new FSFileOperation(networkClient, context);
    }

    private void checkConfig(FSConfig config) {
        if (config == null) {
            throw new FSException("config can not be null");
        }
        if (StrUtil.isBlank(config.getServerUrl())) {
            throw new FSException("host can not be null");
        }
        int port = config.getPort();
        if (port < 0 || port > 65535) {
            throw new InvalidPortException(port, "port must be in range of 1 to 65535");
        }
    }

    private NetworkClient buildClient(OkHttpClient httpClient) {
        if (httpClient != null) {
            return new NetworkClient(httpClient);
        } else {
            List<Protocol> protocol = new LinkedList<>();
            protocol.add(Protocol.HTTP_1_1);
            httpClient = new OkHttpClient();
            httpClient = httpClient.newBuilder()
                    .connectTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                    .protocols(protocol)
                    .retryOnConnectionFailure(false)
                    .build();
            return new NetworkClient(httpClient);
        }
    }



    private HttpUrl buildBaseUrl(String endpoint, int port) {
        if (!StrUtil.startWithIgnoreCase(endpoint, Scheme.HTTP.toString())
                && !StrUtil.startWithIgnoreCase(endpoint, Scheme.HTTPS.toString())) {
            endpoint = "http://" + endpoint;
        }
        HttpUrl url = HttpUrl.parse(endpoint);
        if (url != null) {
            if (!"/".equals(url.encodedPath())) {
                throw new InvalidEndpointException(endpoint, "no path allowed in endpoint");
            }
            HttpUrl.Builder urlBuilder = url.newBuilder();
            if (port > 0) {
                urlBuilder.port(port);
            }
            return  urlBuilder.build();
        }else {
            //ip地址
            if (!Validator.isIpv4(endpoint) && !Validator.isIpv6(endpoint)) {
                throw new InvalidEndpointException(endpoint, "invalid host");
            }
            HttpUrl.Builder builder = new HttpUrl.Builder()
                    .scheme(Scheme.HTTP.toString())
                    .host(endpoint);
           return port == 0 ? builder.build() : builder.port(port).build();
        }
    }




    @Override
    public StorageInfo getStorageInfo() throws IOException {
        Response<StorageInfo> response = this.systemOperation.getStorageInfo();
        return response.getData();
    }

    @Override
    public FileLink fileLink(@Nonnull String serverFilePath) throws IOException {
        if (StrUtil.startWith(serverFilePath, "./")) {
            throw new FSException("The server does not support relative paths");
        }
        if (!StrUtil.startWith(serverFilePath, "/")) {
            serverFilePath = "/" + serverFilePath;
        }
        Response<FileInfo> response = fileOperation.fileLink(serverFilePath);
        FileInfo fileInfo = response.getData();
        return new FileLink(fileInfo, fileOperation);
    }


    @Override
    public FileLink upload(@Nonnull File file, @Nullable String serverDir) throws IOException {
        if (!FileUtil.isFile(file)) {
            throw new FSException(file.getName() + "must be file");
        }
        if (StrUtil.isBlank(serverDir)) {
            serverDir = "/";
        }
        final Response<FileInfo> upload = this.fileOperation.upload(file, serverDir);
        final FileInfo info = upload.getData();
        return new FileLink(info, fileOperation);
    }

    @Override
    public FileLink upload(@Nonnull String filePath, @Nullable String serverDir) throws IOException {
        return this.upload(FileUtil.file(filePath), serverDir);
    }

    @Override
    public List<FileLink> fileLinkList(@Nonnull String serverDir) throws IOException {
        Assert.notBlank(serverDir, "serverDir must not be blank");
        Response<List<FileInfo>> response = fileOperation.listFileLink(serverDir);
        final List<FileInfo> list = response.getData();
        return list.stream().map(fileInfo -> new FileLink(fileInfo, fileOperation)).collect(Collectors.toList());
    }


}
